#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：turbo 
@File    ：mixins.py
@Author  ：赵加响
@Date    ：2023/4/3 14:33 
@Desc    ：DRF通用混入
"""
from datetime import datetime

from django.conf import settings
from django.db import connections
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import FieldError
from rest_framework import mixins
from rest_framework.exceptions import ValidationError

from bases import codes
from bases.messages import MSG_30003_VALIDATE_DESC_KEY_NG, MSG_30004_MODEL_ATTRIBUTE_NG
from bases.response import SuccessResponse
from bases.types import ValidateDescDict


class APIRetrieveModelMixin(mixins.RetrieveModelMixin):
    """
    继承DRF中RetrieveModelMixin，重写父类的retrieve方法
    """

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return SuccessResponse(data=serializer.data, code=codes.CODE_20001_RETRIEVE_OK)


class APIListModelMixin(mixins.ListModelMixin):
    """
    继承DRF中ListModelMixin，重写父类的list方法
    增加对queryset的排序，默认使用id进行倒叙
    """

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        # queryset没有排序时默认对id进行倒叙，解决UnorderedObjectListWarning问题
        if not self.get_queryset().ordered:
            try:
                # id在model中不存在时抛出FieldError异常，并不做任何处理
                queryset = queryset.order_by('-pk')
            except FieldError:
                pass

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return SuccessResponse(data=serializer.data)


class APICreateModelMixin(mixins.CreateModelMixin):
    """
    继承DRF中CreateModelMixin，重写父类的create方法
    """

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return SuccessResponse(data=serializer.data, code=codes.CODE_20002_CREATE_OK, headers=headers)


class APIUpdateModelMixin(mixins.UpdateModelMixin):
    """
    继承DRF中UpdateModelMixin，重写父类的update方法
    """

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return SuccessResponse(data=serializer.data, code=codes.CODE_20003_UPDATE_OK)


class APIDestroyModelMixin(mixins.DestroyModelMixin):
    """
    继承DRF中DestroyModelMixin，重写父类的destroy方法
    在原始删除的基础上增加批量删除功能，当url中存在多个id时默认进行批量删除操作
    instance：在批量删除时，为queryset
    """

    def destroy(self, request, *args, **kwargs):
        if len(kwargs['pk'].split(',')) == 1:
            instance = self.get_object()
        else:
            instance = self.get_object_list()
        self.perform_destroy(instance)
        return SuccessResponse(code=codes.CODE_20004_DELETE_OK)

    # 增强DRF的批量删除功能 ：http请求方法：delete 如： url /api/user/1,2,3/ 批量删除id 1，2，3的用户
    def get_object_list(self):
        queryset = self.filter_queryset(self.get_queryset())
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        assert lookup_url_kwarg in self.kwargs, (
                'Expected view %s to be called with a URL keyword argument '
                'named "%s". Fix your URL conf, or set the `.lookup_field` '
                'attribute on the view correctly.' %
                (self.__class__.__name__, lookup_url_kwarg)
        )
        filter_kwargs = {f"{self.lookup_field}__in": self.kwargs[lookup_url_kwarg].split(',')}
        obj = queryset.filter(**filter_kwargs)
        self.check_object_permissions(self.request, obj)
        return obj


class RawQueryMixin:
    """
    实现原生sql查询混入
    功能：查询条件拼接、排序字段拼接
    """

    """ lookups定义
    - exact: 完全匹配
    - contains: 包含
    - in: 在指定的值列表中
    - gt: 大于
    - gte: 大于等于
    - lt: 小于
    - lte: 小于等于
    """
    LOOKUPS = ['exact', 'contains', 'in', 'gt', 'gte', 'lt', 'lte']
    """
    暂时只支持四种字段类型，number包含包含int和decimal，但是decimal不支持使用in
    """
    TYPES = ['char', 'number', 'date', 'datetime']
    OPERATORS = {
        'char': {
            'exact': '=',
            'in': 'in',
            'contains': 'like'
        },
        'number': {
            'exact': '=',
            'in': 'in',
            'gt': '>',
            'lt': '<',
            'gte': '>=',
            'lte': '<='
        },
        'date': {
            'exact': '=',
            'gt': '>',
            'lt': '<',
            'gte': '>=',
            'lte': '<='
        },
        'datetime': {
            'exact': '=',
            'gt': '>',
            'lt': '<',
            'gte': '>=',
            'lte': '<='
        }
    }

    def _validate_filter_rule(self):
        """
        校验filter_rule属性格式以及type、lookup的填写内容
        @return:
        """
        if self.filter_rule:
            assert len(self.filter_rule) > 0, 'The length of the `filter_rule` must be greater than zero.'
            assert isinstance(self.filter_rule, dict), 'The `filter_rule` must be a dict.'

            for x in self.filter_rule.values():
                if not isinstance(x, dict):
                    raise ValueError('Values in dictionary must be dictionaries')
                if 'field' not in x or 'type' not in x or 'lookup' not in x:
                    raise KeyError("Dictionaries in values must have 'field' and 'type' and 'lookup' keys")

                if x['type'] not in self.TYPES:
                    raise ValueError('`type` is not defined in the system')

                if x['lookup'] not in self.LOOKUPS:
                    raise ValueError('`lookup` is not defined in the system')

    def _splicing_sql_with_filter_rule(self, query_params):
        """
        定义filter_rule时，需要根据定义规则进行拼接SQL
        @param query_params: URL查询参数
        @return:
        """
        conditions = list()

        for field, opts in self.filter_rule.items():
            if field in query_params:
                conditions.append(self._build_condition(opts, query_params[field]))

        return ' and '.join(conditions)

    def _build_condition(self, opts, value):
        field = opts['field']
        field_type = opts['type']
        lookup = opts['lookup']

        template = "{} {} {}"
        operator = self._map_operator(field_type, lookup)
        formatted_value = self._format_value(field_type, lookup, value)

        return template.format(field, operator, formatted_value)

    def _map_operator(self, field_type, lookup):
        return self.OPERATORS[field_type][lookup]

    def _format_value(self, field_type, lookup, value, dt_format=None):
        """
        根据字段类型和过滤方式格式化过滤值
        @param field_type: 字段类型
        @param lookup: 过滤方式
        @param value: GET请求参数值
        @param dt_format: Date、DateTime数据格式
        @return:
        """
        if field_type == 'number':
            if lookup == 'in':
                # 处理number类型字段在使用in，和char类型存在区别，拼接值时没有单引号
                if '[' in value and ']' in value:
                    # GET请求参数使用数组作为查询条件，暂时只支持整数
                    value_list = [int(v.strip("'")) for v in value.strip('[]').split(',')]
                elif '\\n' not in value:
                    # GET请求参数使用换行符作为查询条件
                    return f"({value})"
                else:
                    value_list = value.split('\\n')

                value_set = tuple(int(v) for v in set(value_list))
                if len(value_set) == 1:
                    return f"({value_set[0]})"

                return f"{value_set}"
            else:
                return value

        if field_type in ('date', 'datetime'):
            engine = settings.DATABASES[self.database_name]['ENGINE']
            if engine == 'django.db.backends.mysql':
                return f"'{value}'"
            elif engine == 'django.db.backends.oracle':
                if not dt_format:
                    dt_format = 'YYYY-MM-DD' if field_type == 'date' else 'YYYY-MM-DD HH24:MI:SS'

                return f"to_date('{value}', '{dt_format}')"
            else:
                # 暂时只支持Oracle和MYSQL
                return f"'{value}'"

        if field_type == 'char':
            if lookup == 'contains':
                return f"'%{value}%'"
            elif lookup == 'in':
                # 处理char类型字段在使用in
                if '[' in value and ']' in value:
                    # GET请求参数使用数组作为查询条件
                    value_list = [v.strip("'") for v in value.strip('[]').split(',')]
                elif '\\n' not in value:
                    # GET请求参数使用换行符作为查询条件
                    return f"('{value}')"
                else:
                    value_list = value.split('\\n')

                value_set = tuple(set(value_list))
                if len(value_set) == 1:
                    return f"('{value_set[0]}')"

                return f"{value_set}"
            else:
                return f"'{value}'"

    def validate(self):
        pass

    def execute_raw_query(self, request):
        """
        执行原生sql查询，根据查询请求参数拼接原生sql，查询符合条件的数据
        :return: 字典对象
        """

        if self.raw_query is None:
            raise AttributeError(
                "Attribute Error: when inheriting APIRawQueryViewSet, `raw_query` must be assigned a value ")

        if self.database_name not in list(settings.DATABASES.keys()):
            raise ValueError(
                "Value Error: `database_name` must be exist ")

        self._validate_filter_rule()

        # 排序字段
        sort_field = None
        # 查询参数
        query_params = request.GET
        query_params = query_params.copy()

        # 删除分页、排序参数，这些参数无法拼接在sql中
        if 'page' in query_params:
            del query_params['page']

        if 'page_size' in query_params:
            del query_params['page_size']

        if 'ordering' in query_params:
            sort_field = query_params['ordering']
            del query_params['ordering']

        # 检查原始SQL中是否有GROUP BY子句
        group_by_clause = None
        if 'group by' in self.raw_query.lower():
            # 提取GROUP BY子句
            group_by_clause = self.raw_query.split('group by')[-1].strip()
            self.raw_query = self.raw_query.split('group by')[0].strip()

        order_by_clause = None
        if 'order by' in self.raw_query.lower():
            # 提取GROUP BY子句
            order_by_clause = self.raw_query.split('order by')[-1].strip()
            self.raw_query = self.raw_query.split('order by')[0].strip()

        if 'where' not in self.raw_query.lower():
            self.raw_query += ' where 1=1'

        # 判断是否有查询参数，有的话进行SQL拼接
        if len(query_params) > 0:
            self.raw_query += ' and '
            if self.filter_rule:
                # 如果用户在ViewSet中自定义过滤规则，则使用该逻辑进行SQL拼接
                self.raw_query += self._splicing_sql_with_filter_rule(query_params)
            else:
                self.raw_query += ' and '.join([f"{k} = '{v}'" for k, v in query_params.items()])

        # 如果有GROUP BY子句，重新添加
        if group_by_clause:
            self.raw_query += f' group by {group_by_clause}'

        if order_by_clause:
            self.raw_query += f' order by {order_by_clause}'

        # 判断是否需要进行排序
        if sort_field:
            if sort_field.startswith('-'):
                sort = f' order by {sort_field[1:]} desc'
            else:
                sort = f' order by {sort_field}'

            self.raw_query += sort

        # 执行SQL
        with connections[self.database_name].cursor() as cursor:
            cursor.execute(self.raw_query)
            rows = cursor.fetchall()

            columns = [col[0].lower() for col in cursor.description]

            result = [
                dict(zip(columns, row))
                for row in rows
            ]
            return result


class APIRawQueryMixin(mixins.ListModelMixin, RawQueryMixin):
    """
    继承DRF中ListModelMixin和RawQueryMixin，重写父类的list方法
    适配对字典数据的序列化和分页操作
    """

    def list(self, request, *args, **kwargs):
        self.validate()
        dict_data = self.execute_raw_query(request)
        serializer = self.get_serializer(data=dict_data, many=True)

        serializer.is_valid()
        page = self.paginate_queryset(serializer.data)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(self.customize_response_data(serializer.data))

        return SuccessResponse(data=self.customize_response_data(serializer.data))

    def customize_response_data(self, resp_data):
        """
        在响应数据之前对数据进行自定义处理
        @param resp_data:
        @return:
        """
        return resp_data


class SixFieldsUpdateMixIn:
    """
    业务表更新六大字段混入类
    serializer中create方法使用create_set， update方法使用update_set
    created、updated不再使用外键保存用户ID，而是直接保存用户Username
    """

    def update_set(self, request, validated_data):
        validated_data['updated_id'] = request.user.id
        validated_data['updated_date'] = datetime.now()
        validated_data['updated_program'] = request.method + ' -> ' + request.path
        return validated_data

    def create_set(self, request, validated_data):
        """
        在新建时选择更新六大字段
        """
        validated_data['created_id'] = request.user.id
        validated_data['created_date'] = datetime.now()
        validated_data['created_program'] = request.method + ' -> ' + request.path
        return validated_data


class SerializerValidateMixIn:
    """
    在Serializer中使用validate方法进行字段验证时使用该混入类
    handle_validation负责收集错误字段以及错误信息
    show_validation负责将错误字典返回并由ValidationError抛出
    """

    _error_dict = dict()

    def handle_validation(self, field, message):
        if field not in self._error_dict:
            temp_list = [message]
            self._error_dict[field] = temp_list

    def has_validation(self):
        return True if len(self._error_dict) > 0 else False

    def show_validation(self):
        return self._error_dict

    def clear_validation(self):
        self._error_dict.clear()

    def handle_require_fields(self, serializer_fields, validate_attrs, require_fields: tuple):
        """
        validate方法中判断必输项字段是否为空，serializer中需要重新定义接口字段，并将required设定为False，否则无法走到该检查方法
        调用该方法的前提：model或者serializer中的字段label属性必须指定
        Args:
            serializer_fields: serializer中字段
            validate_attrs: validate方法中数据集
            require_fields: 元组，需要验证的字段

        Returns: 错误数据存入错误字典中

        """
        for field in require_fields:
            label = serializer_fields[field].label
            value = validate_attrs.get(field)

            if not value:
                error_msg = _('%(label)s cannot be empty') % {'label': label}
                self.handle_validation(field, error_msg)

    def handle_non_updatable(self, serializer_fields, instance, validated_data, non_updatable_fields: tuple,
                             kwargs: ValidateDescDict):
        """
        update方法中判断部分状态（例如生效）下，不可修改的字段是否发生了变化
        调用该方法的前提：serializer中的字段label属性必须指定
        Args:
            serializer_fields: serializer中字段
            instance: 实例
            validated_data: 数据集
            non_updatable_fields: 元组，需要验证的字段
            kwargs: {'table': None, 'field': None, 'status': None, 'value': None}

        Returns: 错误数据存入错误字典中
        """
        if getattr(instance, kwargs['field']) in kwargs['value']:
            for field in non_updatable_fields:
                try:
                    label = serializer_fields[field].label
                    orig_value = getattr(instance, field)
                    new_value = validated_data.get(field)
                except KeyError:
                    raise ValidationError(MSG_30003_VALIDATE_DESC_KEY_NG)

                if new_value and orig_value != new_value:
                    error_msg = _(
                        'the data has already been %(status)s in the %(table)s table. %(label)s: %(value)s') % {
                                    'status': kwargs['status'],
                                    'table': kwargs['table'],
                                    'label': label,
                                    'value': new_value
                                }
                    self.handle_validation(field, error_msg)

    def handle_max_length_fields(self, serializer_fields, validated_data):
        """
        validate中验证输入字段的最大长度是否大于定义的最大长度
        Args:
            serializer_fields: serializer中字段
            validated_data: 数据集

        Returns: 错误数据存入错误字典中
        """

        for field in validated_data.keys():
            if hasattr(serializer_fields[field], 'max_length'):
                value = validated_data.get(field)
                label = serializer_fields[field].label
                max_length = serializer_fields[field].max_length

                if len(value) > max_length:
                    error_msg = _('%(field)s cannot be greater than %(length)s') % {'field': label,
                                                                                    'length': str(max_length)}
                    self.handle_validation(field, error_msg)

    def handle_unique_fields(self, model, serializer_fields, validated_data, unique_fields: tuple, instance=None,
                             update=False):
        """
        检查unique字段是否在数据库中已经存在
        Args:
            model: Model
            serializer_fields: serializer中字段
            validated_data: 数据集
            unique_fields: 元组，需要检查唯一存在的字段，元素只可以为字符串和元组
            instance: 数据更新时的实例
            update: 是否进行数据更新

        Returns: 错误数据存入错误字典中
        """

        for field in unique_fields:

            if isinstance(field, tuple):
                value_list = list()
                label_list = list()
                try:
                    for subfield in field:
                        value = validated_data.get(subfield)
                        value_list.append(value)
                        label_list.append(serializer_fields[subfield].label)

                    kwargs = dict(zip(field, tuple(value_list)))
                    if update:
                        exist_data = model.objects.exclude(pk=instance.pk).filter(**kwargs)
                    else:
                        exist_data = model.objects.filter(**kwargs)

                    if exist_data.exists():
                        error_msg = _('%(label)s: %(value)s already exists in the system') % {
                            'label': ','.join(label_list),
                            'value': ','.join(value_list)}
                        self.handle_validation(field[0], error_msg)
                except KeyError:
                    raise ValidationError(MSG_30004_MODEL_ATTRIBUTE_NG)

            elif isinstance(field, str):
                try:
                    value = validated_data.get(field)
                    label = serializer_fields[field].label
                except KeyError:
                    raise ValidationError(MSG_30004_MODEL_ATTRIBUTE_NG)

                kwargs = dict(zip((field,), (value,)))
                if update:
                    exist_data = model.objects.exclude(pk=instance.pk).filter(**kwargs)
                else:
                    exist_data = model.objects.filter(**kwargs)

                if exist_data.exists():
                    error_msg = _('%(label)s: %(value)s already exists in the system') % {'label': label,
                                                                                          'value': value}
                    self.handle_validation(field, error_msg)

            else:
                raise ValueError(
                    "System Error: The unique fields object can only contain strings and tuples."
                    "(bases/serializers.py -> unique_fields)")
